<?php
/**
 * 栖岛API OAuth2 SDK
 * 
 * 一个简单易用的栖岛API OAuth2客户端SDK，支持OAuth2授权流程、用户信息获取、授权管理等功能
 * 
 * @version 1.0.0
 * @author SDK Generator
 * @license MIT
 * 时间：2025-07-22
 * 作者：栖岛-艺当然
 */

class QidaoOAuth2SDK {
    /**
     * API基础URL
     * @var string
     */
    private $apiBaseUrl = 'https://api.qidao.tvcloud.top';

    /**
     * 调试模式
     * @var bool
     */
    private $debug = false;
    
    /**
     * 授权端点
     * @var string
     */
    private $authorizeEndpoint = '/oauth2/authorize';
    
    /**
     * 令牌端点
     * @var string
     */
    private $tokenEndpoint = '/oauth2/token';
    
    /**
     * 用户信息端点
     * @var string
     */
    private $userInfoEndpoint = '/oauth2/userinfo/oauth';
    
    /**
     * 撤销令牌端点
     * @var string
     */
    private $tokenRevokeEndpoint = '/oauth2/revoke';
    
    /**
     * 令牌信息端点
     * @var string
     */
    private $tokenInfoEndpoint = '/oauth2/token/info';
    
    /**
     * 客户端信息端点
     * @var string
     */
    private $clientInfoEndpoint = '/oauth2/client/info';
    
    /**
     * 客户端ID
     * @var string
     */
    private $clientId;
    
    /**
     * 客户端密钥
     * @var string
     */
    private $clientSecret;
    
    /**
     * 重定向URI
     * @var string
     */
    private $redirectUri;
    
    /**
     * 授权作用域
     * @var string
     */
    private $scope;
    
    /**
     * 状态参数
     * @var string
     */
    public $state;
    
    
    /**
     * 构造函数
     * 
     * @param array $config 配置参数数组
     *        - client_id     : 客户端ID (必填)
     *        - client_secret : 客户端密钥 (必填)
     *        - redirect_uri  : 重定向URI (可选)
     *        - scope         : 授权作用域，多个值用空格分隔 (可选)
     *        - api_base_url  : 自定义API基础URL (可选)
     *        - state         : 自定义状态参数 (可选)
     *        - debug         : 是否开启调试模式 (可选，默认false)
     * @throws InvalidArgumentException 如果缺少必要参数
     */
    public function __construct($config) {
        if (!isset($config['client_id']) || !isset($config['client_secret'])) {
            throw new InvalidArgumentException('必须提供client_id和client_secret');
        }
        
        $this->clientId = $config['client_id'];
        $this->clientSecret = $config['client_secret'];
        $this->redirectUri = isset($config['redirect_uri']) ? $config['redirect_uri'] : '';
        $this->scope = isset($config['scope']) ? $config['scope'] : '';
        $this->state = isset($config['state']) ? $config['state'] : $this->generateState();
        
        if (isset($config['api_base_url'])) {
            $this->apiBaseUrl = rtrim($config['api_base_url'], '/');
        }
        
        if (isset($config['debug']) && $config['debug'] === true) {
            $this->debug = true;
        }
    }
    
    /**
     * 生成随机状态码
     * @return string
     */
    private function generateState() {
        return md5(uniqid(rand(), true));
    }
    
    /**
     * 获取授权URL
     * 
     * 生成用于重定向用户到栖岛授权页面的URL
     * 
     * @param array $params 额外参数 (可选)
     * @return string 完整的授权URL
     */
    public function getAuthorizationUrl($params = []) {
        $defaults = [
            'client_id' => $this->clientId,
            'redirect_uri' => $this->redirectUri,
            'response_type' => 'code',
            'scope' => $this->scope,
            'state' => $this->state,
        ];
        
        $params = array_merge($defaults, $params);
        return $this->apiBaseUrl . $this->authorizeEndpoint . '?' . http_build_query($params);
    }
    
    /**
     * 使用授权码获取访问令牌
     * 为了解决特定格式问题，这个方法使用专用的HTTP请求直接处理OAuth2令牌响应
     * 
     * @param string $code 授权码
     * @return array 令牌数据，包含access_token, refresh_token, expires_in等字段
     * @throws Exception 请求失败或响应错误时抛出
     */
    public function getAccessToken($code) {
        $url = $this->apiBaseUrl . $this->tokenEndpoint;
        $postFields = http_build_query([
            'grant_type' => 'authorization_code',
            'code' => $code,
            'client_id' => $this->clientId,
            'client_secret' => $this->clientSecret,
            'redirect_uri' => $this->redirectUri,
        ]);
        
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $postFields,
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/x-www-form-urlencoded',
                'Accept: application/json'
            ],
            CURLOPT_TIMEOUT => 30,
            CURLOPT_SSL_VERIFYPEER => true
        ]);
        
        // 调试输出
        if ($this->debug) {
            error_log("[OAuth2SDK Debug] 获取访问令牌: POST $url");
            error_log("[OAuth2SDK Debug] 请求参数: " . $postFields);
            curl_setopt($ch, CURLOPT_VERBOSE, true);
            $verbose = fopen('php://temp', 'w+');
            curl_setopt($ch, CURLOPT_STDERR, $verbose);
        }
        
        $response = curl_exec($ch);
        $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        $errno = curl_errno($ch);
        
        // 调试输出
        if ($this->debug) {
            error_log("[OAuth2SDK Debug] 访问令牌响应状态: $statusCode");
            error_log("[OAuth2SDK Debug] 原始响应: " . $response);
            if ($error) {
                error_log("[OAuth2SDK Debug] CURL错误 ($errno): $error");
            }
            rewind($verbose);
            $verboseLog = stream_get_contents($verbose);
            if ($verboseLog) {
                error_log("[OAuth2SDK Debug] CURL详情: $verboseLog");
            }
            fclose($verbose);
        }
        
        curl_close($ch);
        
        // 处理CURL错误
        if ($response === false) {
            throw new Exception("获取访问令牌请求失败: $error (cURL错误: $errno)");
        }
        
        // 检查HTTP状态码
        if ($statusCode < 200 || $statusCode >= 300) {
            throw new Exception("获取访问令牌HTTP错误: 状态码 $statusCode, 响应: $response");
        }
        
        // 尝试解析JSON响应
        $decoded = json_decode($response, true);
        
        // 检查是否成功解析JSON
        if (json_last_error() !== JSON_ERROR_NONE) {
            error_log("[OAuth2SDK Debug] JSON解析错误: " . json_last_error_msg());
            error_log("[OAuth2SDK Debug] 尝试手动解析响应");
            
            // 手动尝试提取访问令牌信息
            $token = [];
            
            if (preg_match('/"access_token"\s*:\s*"([^"]+)"/', $response, $matches)) {
                $token['access_token'] = $matches[1];
            }
            
            if (preg_match('/"refresh_token"\s*:\s*"([^"]+)"/', $response, $matches)) {
                $token['refresh_token'] = $matches[1];
            }
            
            if (preg_match('/"expires_in"\s*:\s*(\d+)/', $response, $matches)) {
                $token['expires_in'] = (int)$matches[1];
            }
            
            if (preg_match('/"token_type"\s*:\s*"([^"]+)"/', $response, $matches)) {
                $token['token_type'] = $matches[1];
            }
            
            if (preg_match('/"scope"\s*:\s*"([^"]+)"/', $response, $matches)) {
                $token['scope'] = $matches[1];
            }
            
            if (isset($token['access_token'])) {
                return $token;
            }
            
            throw new Exception("获取访问令牌失败: 无法解析响应数据 - $response");
        }
        
        // 检查解析后的JSON是否包含access_token
        if (isset($decoded['access_token'])) {
            return $decoded;
        }
        
        // 检查栖岛API的返回格式
        if (isset($decoded['code']) && $decoded['code'] == 1 && isset($decoded['data'])) {
            return $decoded['data'];
        }
        
        // 构建错误信息
        $errorMsg = '获取访问令牌失败: ';
        if (isset($decoded['msg'])) {
            $errorMsg .= $decoded['msg'];
        } else if (isset($decoded['error'])) {
            $errorMsg .= $decoded['error'];
            if (isset($decoded['error_description'])) {
                $errorMsg .= ' - ' . $decoded['error_description'];
            }
        } else {
            $errorMsg .= '未知错误，完整响应: ' . $response;
        }
        
        throw new Exception($errorMsg);
    }
    
    /**
     * 刷新访问令牌
     * 使用与getAccessToken相同的专用实现来处理响应
     * 
     * @param string $refreshToken 刷新令牌
     * @return array 新的令牌数据
     * @throws Exception 请求失败或响应错误时抛出
     */
    public function refreshToken($refreshToken) {
        $url = $this->apiBaseUrl . $this->tokenEndpoint;
        $postFields = http_build_query([
            'grant_type' => 'refresh_token',
            'refresh_token' => $refreshToken,
            'client_id' => $this->clientId,
            'client_secret' => $this->clientSecret,
        ]);
        
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $postFields,
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/x-www-form-urlencoded',
                'Accept: application/json'
            ],
            CURLOPT_TIMEOUT => 30,
            CURLOPT_SSL_VERIFYPEER => true
        ]);
        
        // 调试输出
        if ($this->debug) {
            error_log("[OAuth2SDK Debug] 刷新令牌: POST $url");
            error_log("[OAuth2SDK Debug] 请求参数: " . $postFields);
            curl_setopt($ch, CURLOPT_VERBOSE, true);
            $verbose = fopen('php://temp', 'w+');
            curl_setopt($ch, CURLOPT_STDERR, $verbose);
        }
        
        $response = curl_exec($ch);
        $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        $errno = curl_errno($ch);
        
        // 调试输出
        if ($this->debug) {
            error_log("[OAuth2SDK Debug] 刷新令牌响应状态: $statusCode");
            error_log("[OAuth2SDK Debug] 原始响应: " . $response);
            if ($error) {
                error_log("[OAuth2SDK Debug] CURL错误 ($errno): $error");
            }
            rewind($verbose);
            $verboseLog = stream_get_contents($verbose);
            if ($verboseLog) {
                error_log("[OAuth2SDK Debug] CURL详情: $verboseLog");
            }
            fclose($verbose);
        }
        
        curl_close($ch);
        
        // 处理CURL错误
        if ($response === false) {
            throw new Exception("刷新访问令牌请求失败: $error (cURL错误: $errno)");
        }
        
        // 检查HTTP状态码
        if ($statusCode < 200 || $statusCode >= 300) {
            throw new Exception("刷新访问令牌HTTP错误: 状态码 $statusCode, 响应: $response");
        }
        
        // 尝试解析JSON响应
        $decoded = json_decode($response, true);
        
        // 检查是否成功解析JSON
        if (json_last_error() !== JSON_ERROR_NONE) {
            error_log("[OAuth2SDK Debug] JSON解析错误: " . json_last_error_msg());
            error_log("[OAuth2SDK Debug] 尝试手动解析响应");
            
            // 手动尝试提取访问令牌信息
            $token = [];
            
            if (preg_match('/"access_token"\s*:\s*"([^"]+)"/', $response, $matches)) {
                $token['access_token'] = $matches[1];
            }
            
            if (preg_match('/"refresh_token"\s*:\s*"([^"]+)"/', $response, $matches)) {
                $token['refresh_token'] = $matches[1];
            }
            
            if (preg_match('/"expires_in"\s*:\s*(\d+)/', $response, $matches)) {
                $token['expires_in'] = (int)$matches[1];
            }
            
            if (preg_match('/"token_type"\s*:\s*"([^"]+)"/', $response, $matches)) {
                $token['token_type'] = $matches[1];
            }
            
            if (preg_match('/"scope"\s*:\s*"([^"]+)"/', $response, $matches)) {
                $token['scope'] = $matches[1];
            }
            
            if (isset($token['access_token'])) {
                return $token;
            }
            
            throw new Exception("刷新访问令牌失败: 无法解析响应数据 - $response");
        }
        
        // 检查解析后的JSON是否包含access_token
        if (isset($decoded['access_token'])) {
            return $decoded;
        }
        
        // 检查栖岛API的返回格式
        if (isset($decoded['code']) && $decoded['code'] == 1 && isset($decoded['data'])) {
            return $decoded['data'];
        }
        
        // 构建错误信息
        $errorMsg = '刷新访问令牌失败: ';
        if (isset($decoded['msg'])) {
            $errorMsg .= $decoded['msg'];
        } else if (isset($decoded['error'])) {
            $errorMsg .= $decoded['error'];
            if (isset($decoded['error_description'])) {
                $errorMsg .= ' - ' . $decoded['error_description'];
            }
        } else {
            $errorMsg .= '未知错误，完整响应: ' . $response;
        }
        
        throw new Exception($errorMsg);
    }
    
    /**
     * 获取用户信息
     * 使用专用实现来处理令牌和响应
     * 
     * @param string|array $accessToken 访问令牌或包含访问令牌的数组
     * @return array 用户信息数据
     * @throws Exception 请求失败或响应错误时抛出
     */
    public function getUserInfo($accessToken) {
        // 调试日志
        if ($this->debug) {
            error_log("[OAuth2SDK Debug] getUserInfo被调用，参数类型: " . gettype($accessToken));
            if (is_array($accessToken)) {
                error_log("[OAuth2SDK Debug] accessToken数组内容: " . print_r($accessToken, true));
            } else {
                error_log("[OAuth2SDK Debug] accessToken值: " . $accessToken);
            }
        }
        
        // 处理令牌格式
        if (is_array($accessToken) && isset($accessToken['access_token'])) {
            $tokenValue = $accessToken['access_token'];
        } else {
            $tokenValue = $accessToken;
        }
        
        // 验证令牌不为空
        if (empty($tokenValue)) {
            throw new Exception('获取用户信息失败: access_token不能为空，请确保传入了有效的访问令牌');
        }
        
        // 构建请求
        $url = $this->apiBaseUrl . $this->userInfoEndpoint;
        $headers = [
            'Accept: application/json',
            'Authorization: Bearer ' . $tokenValue
        ];
        
        // 如果是查询参数方式，则添加到URL
        if (strpos($url, '?') !== false) {
            $url .= '&access_token=' . urlencode($tokenValue);
        } else {
            $url .= '?access_token=' . urlencode($tokenValue);
        }
        
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_USERAGENT => 'QidaoOAuth2SDK/1.0',
            CURLOPT_TIMEOUT => 30,
            CURLOPT_SSL_VERIFYPEER => true
        ]);
        
        // 调试输出
        if ($this->debug) {
            error_log("[OAuth2SDK Debug] 获取用户信息: GET $url");
            error_log("[OAuth2SDK Debug] 请求头: " . json_encode($headers));
            curl_setopt($ch, CURLOPT_VERBOSE, true);
            $verbose = fopen('php://temp', 'w+');
            curl_setopt($ch, CURLOPT_STDERR, $verbose);
        }
        
        $response = curl_exec($ch);
        $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        $errno = curl_errno($ch);
        
        // 调试输出
        if ($this->debug) {
            error_log("[OAuth2SDK Debug] 用户信息响应状态: $statusCode");
            error_log("[OAuth2SDK Debug] 原始响应: " . $response);
            if ($error) {
                error_log("[OAuth2SDK Debug] CURL错误 ($errno): $error");
            }
            rewind($verbose);
            $verboseLog = stream_get_contents($verbose);
            if ($verboseLog) {
                error_log("[OAuth2SDK Debug] CURL详情: $verboseLog");
            }
            fclose($verbose);
        }
        
        curl_close($ch);
        
        // 处理CURL错误
        if ($response === false) {
            throw new Exception("获取用户信息请求失败: $error (cURL错误: $errno)");
        }
        
        // 检查HTTP状态码
        if ($statusCode < 200 || $statusCode >= 300) {
            throw new Exception("获取用户信息HTTP错误: 状态码 $statusCode, 响应: $response");
        }
        
        // 尝试解析JSON响应
        $decoded = json_decode($response, true);
        
        // 检查是否成功解析JSON
        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new Exception("获取用户信息失败: 无法解析JSON响应 - $response");
        }
        
        // 检查栖岛API的返回格式
        if (isset($decoded['code']) && $decoded['code'] == 1 && isset($decoded['data'])) {
            return $decoded['data'];
        }
        
        // 如果是直接返回用户信息的标准格式
        if (isset($decoded['uid']) || isset($decoded['id']) || isset($decoded['name'])) {
            return $decoded;
        }
        
        // 构建错误信息
        $errorMsg = '获取用户信息失败: ';
        if (isset($decoded['msg'])) {
            $errorMsg .= $decoded['msg'];
        } else if (isset($decoded['error'])) {
            $errorMsg .= $decoded['error'];
            if (isset($decoded['error_description'])) {
                $errorMsg .= ' - ' . $decoded['error_description'];
            }
        } else {
            $errorMsg .= '未知错误，完整响应: ' . $response;
        }
        
        throw new Exception($errorMsg);
    }
    
    /**
     * 获取用户授权列表
     * 使用专用实现来处理令牌和响应
     * 
     * @param string|array $accessToken 访问令牌或包含访问令牌的数组
     * @return array 授权列表数据
     * @throws Exception 请求失败或响应错误时抛出
     */
    public function getUserAuthorizations($accessToken) {
        // 处理令牌格式
        if (is_array($accessToken) && isset($accessToken['access_token'])) {
            $tokenValue = $accessToken['access_token'];
        } else {
            $tokenValue = $accessToken;
        }
        
        // 验证令牌不为空
        if (empty($tokenValue)) {
            throw new Exception('获取授权列表失败: access_token不能为空');
        }
        
        // 构建请求
        $url = $this->apiBaseUrl . $this->authorizationsEndpoint;
        $headers = [
            'Accept: application/json',
            'Authorization: Bearer ' . $tokenValue
        ];
        
        // 如果是查询参数方式，则添加到URL
        if (strpos($url, '?') !== false) {
            $url .= '&access_token=' . urlencode($tokenValue);
        } else {
            $url .= '?access_token=' . urlencode($tokenValue);
        }
        
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_USERAGENT => 'QidaoOAuth2SDK/1.0',
            CURLOPT_TIMEOUT => 30,
            CURLOPT_SSL_VERIFYPEER => true
        ]);
        
        // 调试输出
        if ($this->debug) {
            error_log("[OAuth2SDK Debug] 获取授权列表: GET $url");
            error_log("[OAuth2SDK Debug] 请求头: " . json_encode($headers));
            curl_setopt($ch, CURLOPT_VERBOSE, true);
            $verbose = fopen('php://temp', 'w+');
            curl_setopt($ch, CURLOPT_STDERR, $verbose);
        }
        
        $response = curl_exec($ch);
        $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        $errno = curl_errno($ch);
        
        // 调试输出
        if ($this->debug) {
            error_log("[OAuth2SDK Debug] 授权列表响应状态: $statusCode");
            error_log("[OAuth2SDK Debug] 原始响应: " . $response);
            if ($error) {
                error_log("[OAuth2SDK Debug] CURL错误 ($errno): $error");
            }
            rewind($verbose);
            $verboseLog = stream_get_contents($verbose);
            if ($verboseLog) {
                error_log("[OAuth2SDK Debug] CURL详情: $verboseLog");
            }
            fclose($verbose);
        }
        
        curl_close($ch);
        
        // 处理CURL错误
        if ($response === false) {
            throw new Exception("获取授权列表请求失败: $error (cURL错误: $errno)");
        }
        
        // 检查HTTP状态码
        if ($statusCode < 200 || $statusCode >= 300) {
            throw new Exception("获取授权列表HTTP错误: 状态码 $statusCode, 响应: $response");
        }
        
        // 尝试解析JSON响应
        $decoded = json_decode($response, true);
        
        // 检查是否成功解析JSON
        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new Exception("获取授权列表失败: 无法解析JSON响应 - $response");
        }
        
        // 检查栖岛API的返回格式
        if (isset($decoded['code']) && $decoded['code'] == 1 && isset($decoded['data'])) {
            return $decoded['data'];
        }
        
        // 构建错误信息
        $errorMsg = '获取授权列表失败: ';
        if (isset($decoded['msg'])) {
            $errorMsg .= $decoded['msg'];
        } else if (isset($decoded['error'])) {
            $errorMsg .= $decoded['error'];
            if (isset($decoded['error_description'])) {
                $errorMsg .= ' - ' . $decoded['error_description'];
            }
        } else {
            $errorMsg .= '未知错误，完整响应: ' . $response;
        }
        
        throw new Exception($errorMsg);
    }
    
    /**
     * 撤销特定授权
     * 使用专用实现来处理令牌和响应
     * 
     * @param string|array $accessToken 访问令牌或包含访问令牌的数组
     * @param string $authorizationId 授权ID
     * @return bool 操作是否成功
     * @throws Exception 请求失败或响应错误时抛出
     */
    public function revokeAuthorization($accessToken, $authorizationId) {
        // 处理令牌格式
        if (is_array($accessToken) && isset($accessToken['access_token'])) {
            $tokenValue = $accessToken['access_token'];
        } else {
            $tokenValue = $accessToken;
        }
        
        // 验证令牌不为空
        if (empty($tokenValue)) {
            throw new Exception('撤销授权失败: access_token不能为空');
        }
        
        // 验证授权ID不为空
        if (empty($authorizationId)) {
            throw new Exception('撤销授权失败: 授权ID不能为空');
        }
        
        // 构建请求
        $url = $this->apiBaseUrl . $this->revokeEndpoint;
        $headers = [
            'Content-Type: application/x-www-form-urlencoded',
            'Accept: application/json',
            'Authorization: Bearer ' . $tokenValue
        ];
        
        $params = ['id' => $authorizationId];
        $postFields = http_build_query($params);
        
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $postFields,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_USERAGENT => 'QidaoOAuth2SDK/1.0',
            CURLOPT_TIMEOUT => 30,
            CURLOPT_SSL_VERIFYPEER => true
        ]);
        
        // 调试输出
        if ($this->debug) {
            error_log("[OAuth2SDK Debug] 撤销授权: POST $url");
            error_log("[OAuth2SDK Debug] 请求参数: " . $postFields);
            error_log("[OAuth2SDK Debug] 请求头: " . json_encode($headers));
            curl_setopt($ch, CURLOPT_VERBOSE, true);
            $verbose = fopen('php://temp', 'w+');
            curl_setopt($ch, CURLOPT_STDERR, $verbose);
        }
        
        $response = curl_exec($ch);
        $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        $errno = curl_errno($ch);
        
        // 调试输出
        if ($this->debug) {
            error_log("[OAuth2SDK Debug] 撤销授权响应状态: $statusCode");
            error_log("[OAuth2SDK Debug] 原始响应: " . $response);
            if ($error) {
                error_log("[OAuth2SDK Debug] CURL错误 ($errno): $error");
            }
            rewind($verbose);
            $verboseLog = stream_get_contents($verbose);
            if ($verboseLog) {
                error_log("[OAuth2SDK Debug] CURL详情: $verboseLog");
            }
            fclose($verbose);
        }
        
        curl_close($ch);
        
        // 处理CURL错误
        if ($response === false) {
            throw new Exception("撤销授权请求失败: $error (cURL错误: $errno)");
        }
        
        // 检查HTTP状态码
        if ($statusCode < 200 || $statusCode >= 300) {
            throw new Exception("撤销授权HTTP错误: 状态码 $statusCode, 响应: $response");
        }
        
        // 尝试解析JSON响应
        $decoded = json_decode($response, true);
        
        // 检查是否成功解析JSON
        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new Exception("撤销授权失败: 无法解析JSON响应 - $response");
        }
        
        // 检查栖岛API的返回格式
        if (isset($decoded['code']) && $decoded['code'] == 1) {
            return true;
        }
        
        // 构建错误信息
        $errorMsg = '撤销授权失败: ';
        if (isset($decoded['msg'])) {
            $errorMsg .= $decoded['msg'];
        } else if (isset($decoded['error'])) {
            $errorMsg .= $decoded['error'];
            if (isset($decoded['error_description'])) {
                $errorMsg .= ' - ' . $decoded['error_description'];
            }
        } else {
            $errorMsg .= '未知错误，完整响应: ' . $response;
        }
        
        throw new Exception($errorMsg);
    }
    
    /**
     * 验证状态参数，防止CSRF攻击
     * 
     * @param string $state 回调中返回的状态参数
     * @return bool 状态参数是否匹配
     */
    public function validateState($state) {
        return $this->state === $state;
    }
    
    /**
     * 发送API请求
     * 
     * @param string $method HTTP方法(GET, POST等)
     * @param string $endpoint API端点
     * @param array $params 请求参数
     * @param string|array $accessToken 访问令牌(可选)
     * @return array 解析后的API响应
     * @throws Exception 请求失败或解析错误时抛出
     */
    private function request($method, $endpoint, $params = [], $accessToken = null) {
        $url = $this->apiBaseUrl . $endpoint;
        $headers = ['Content-Type: application/x-www-form-urlencoded', 'Accept: application/json'];
        
        if ($accessToken) {
            if (is_array($accessToken) && isset($accessToken['access_token'])) {
                $accessToken = $accessToken['access_token'];
            }
            $headers[] = 'Authorization: Bearer ' . $accessToken;
        }
        
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_USERAGENT => 'QidaoOAuth2SDK/1.0',
            CURLOPT_CONNECTTIMEOUT => 30,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_HEADER => true, // 获取响应头
        ];
        
        if ($method === 'POST') {
            $options[CURLOPT_POST] = true;
            $options[CURLOPT_POSTFIELDS] = http_build_query($params);
        } else if ($method === 'GET' && !empty($params)) {
            $options[CURLOPT_URL] = $url . '?' . http_build_query($params);
        }
        
        // 记录请求详情
        if ($this->debug) {
            error_log("QidaoOAuth2SDK请求: $method $url");
            error_log("请求头: " . json_encode($headers));
            if (!empty($params)) {
                error_log("请求参数: " . json_encode($params));
            }
        }
        
        $curl = curl_init();
        curl_setopt_array($curl, $options);
        
        // 添加更多调试选项
        if ($this->debug) {
            curl_setopt($curl, CURLOPT_VERBOSE, true);
            $verbose = fopen('php://temp', 'w+');
            curl_setopt($curl, CURLOPT_STDERR, $verbose);
        }
        
        $rawResponse = curl_exec($curl);
        $headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
        $statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
        $contentType = curl_getinfo($curl, CURLINFO_CONTENT_TYPE);
        $error = curl_error($curl);
        $errno = curl_errno($curl);
        
        // 分离响应头和响应体
        $headers = substr($rawResponse, 0, $headerSize);
        $response = substr($rawResponse, $headerSize);
        
        // 增强的调试日志
        if ($this->debug) {
            error_log("QidaoOAuth2SDK响应: 状态码=$statusCode, Content-Type=$contentType");
            error_log("响应头: $headers");
            error_log("响应体: $response");
            
            if ($error) {
                error_log("cURL错误 ($errno): $error");
            }
            
            // 输出详细的cURL调试信息
            rewind($verbose);
            $verboseLog = stream_get_contents($verbose);
            if ($verboseLog) {
                error_log("cURL详细日志:\n$verboseLog");
            }
            fclose($verbose);
        }
        
        curl_close($curl);
        
        if ($rawResponse === false) {
            throw new Exception("API请求失败: $error (cURL错误: $errno)");
        }
        
        // 基于Content-Type处理响应
        $isJsonResponse = preg_match('/application\/json/i', $contentType);
        
        if ($isJsonResponse) {
            // 尝试解析JSON
            $result = json_decode($response, true);
            $jsonError = json_last_error();
            
            if ($jsonError !== JSON_ERROR_NONE) {
                $errorMsg = 'JSON解析错误 (' . json_last_error_msg() . '): ' . $response;
                if ($this->debug) {
                    error_log($errorMsg);
                }
                // 检查特殊情况：响应是有效的OAuth2令牌格式但JSON解析出错
                if (preg_match('/"access_token":\s*"([^"]+)"/', $response, $matches)) {
                    // 手动提取令牌信息
                    $token = [];
                    preg_match('/"access_token":\s*"([^"]+)"/', $response, $access);
                    preg_match('/"refresh_token":\s*"([^"]+)"/', $response, $refresh);
                    preg_match('/"expires_in":\s*(\d+)/', $response, $expires);
                    preg_match('/"token_type":\s*"([^"]+)"/', $response, $type);
                    preg_match('/"scope":\s*"([^"]+)"/', $response, $scope);
                    
                    if (isset($access[1])) $token['access_token'] = $access[1];
                    if (isset($refresh[1])) $token['refresh_token'] = $refresh[1];
                    if (isset($expires[1])) $token['expires_in'] = (int)$expires[1];
                    if (isset($type[1])) $token['token_type'] = $type[1];
                    if (isset($scope[1])) $token['scope'] = $scope[1];
                    
                    if (!empty($token['access_token'])) {
                        return $token;
                    }
                }
                throw new Exception($errorMsg);
            }
            
            return $result;
        } else {
            // 非JSON响应，直接返回响应内容
            return ['raw_response' => $response, 'status_code' => $statusCode];
        }
    }
    
    /**
     * 将用户数据转换为对象
     * 
     * @param array $userData 用户数据
     * @return object 包含用户属性的对象
     */
    public function createUser($userData) {
        return (object) [
            'id' => isset($userData['uid']) ? $userData['uid'] : null,
            'name' => isset($userData['name']) ? $userData['name'] : null,
            'screenName' => isset($userData['screenName']) ? $userData['screenName'] : null,
            'email' => isset($userData['mail']) ? $userData['mail'] : null,
            'avatar' => isset($userData['avatar']) ? $userData['avatar'] : null,
            'introduce' => isset($userData['introduce']) ? $userData['introduce'] : null,
            'group' => isset($userData['group']) ? $userData['group'] : null,
            'createdTime' => isset($userData['created']) ? $userData['created'] : null,
        ];
    }
    
    /**
     * 设置调试模式
     * 
     * @param bool $debug 是否开启调试
     * @return self
     */
    public function setDebug($debug) {
        $this->debug = (bool) $debug;
        return $this;
    }
    
    /**
     * 获取客户端信息
     * 
     * @param string $clientId 客户端ID（可选，默认使用当前客户端ID）
     * @return array 客户端信息
     * @throws Exception 请求失败或响应错误时抛出
     */
    public function getClientInfo($clientId = null) {
        if ($clientId === null) {
            $clientId = $this->clientId;
        }
        
        $params = ['client_id' => $clientId];
        $response = $this->request('GET', $this->clientInfoEndpoint, $params);
        
        if (isset($response['code']) && $response['code'] == 1 && isset($response['data'])) {
            return $response['data'];
        }
        
        throw new Exception('获取客户端信息失败: ' . (isset($response['msg']) ? $response['msg'] : '未知错误'));
    }
    
    /**
     * 获取令牌信息
     * 
     * @param string $accessToken 访问令牌
     * @return array 令牌信息
     * @throws Exception 请求失败或响应错误时抛出
     */
    public function getTokenInfo($accessToken) {
        $params = ['token' => $accessToken];
        $response = $this->request('GET', $this->tokenInfoEndpoint, $params);
        
        if (isset($response['code']) && $response['code'] == 1 && isset($response['data'])) {
            return $response['data'];
        }
        
        throw new Exception('获取令牌信息失败: ' . (isset($response['msg']) ? $response['msg'] : '未知错误'));
    }
    
    /**
     * 撤销访问令牌
     * 
     * @param string $token 要撤销的令牌
     * @param string $tokenTypeHint 令牌类型提示（可选，如access_token或refresh_token）
     * @return bool 撤销是否成功
     * @throws Exception 请求失败或响应错误时抛出
     */
    public function revokeToken($token, $tokenTypeHint = null) {
        $params = [
            'token' => $token,
            'client_id' => $this->clientId,
            'client_secret' => $this->clientSecret,
        ];
        
        if ($tokenTypeHint !== null) {
            $params['token_type_hint'] = $tokenTypeHint;
        }
        
        $response = $this->request('POST', $this->tokenRevokeEndpoint, $params);
        
        // 撤销成功时，返回空对象
        if (is_array($response) && empty($response)) {
            return true;
        }
        
        // 如果返回了错误信息
        if (isset($response['error'])) {
            throw new Exception('撤销令牌失败: ' . $response['error']);
        }
        
        return false;
    }
} 